You probably already have seen several definitions of serverless and reasons for using it, but since it also plays an important role in authentication, here are some inherent core features:
- Auto-scaling incl. “scale to zero”
- High availability
- Pay on demand, i.e. no costs when not in use
- A scheduler outside our area of responsibility calls our code; this is not the case with Kubernetes – managed or otherwise.
All this is delivered to us ready to use without having to worry about the technical details of the underlying server infrastructure – hence the name serverless. We only need to configure certain key data on a pleasantly abstract level. For example, we might want to give the automatic scaling an upper limit in order to avoid nasty surprises regarding costs if product interest spikes suddenly or we experience a DDoS attack. After all, there is no longer the technical scaling limit of the one server we deployed. As you can see, some serverless features can be a curse and a blessing at the same time. It is also undeniable that with serverless, architectural knowledge and experience have not just become more important, but that a solid grounding in the basics is something you should have.
Instead of assuming responsibility for traditional low-level aspects such as the operating system and network, installing VMs, deploying containers or pods, and general “infrastructure babysitting”, we prefer to hand all of this to the cloud provider of our choice. With serverless, we use High Level Managed Functions, which fulfill the above mentioned aspects. They can withstand just about any load, are highly available and cost us not a cent without a load. All we have to do is to connect these services and let them communicate with one another. This can be done more frequently via a configuration, instead of having to write the code itself.
Azure Functions — with which we are going to create the API subsequently — belong to the Compute category. But there are also managed serverless service offers in other categories. In the area of Storage – which was available long before Azure Functions – Azure Blob Storage is the first to be mentioned. In Messaging, Azure Cloud offers services such as Azure Service Bus or Azure Event Grid.
There are many more offers, besides these three well-known categories. Here are just a few more examples of further fields:
- Secrets management systems (for example, Azure Key Vault)
- Streaming or stream analysis (for example, Azure Event Hub and Azure Stream Analytics)
- Artificial intelligence (for example, Azure Cognitive Services) and so forth.
These managed serverless services will develop even more strongly in the future towards specialized, combinable business components, such as Amazon Pinpoint for marketing campaigns. The focus is on reducing the total cost of ownership of a product. We want to solve business problems in our specialist domain and write code for business logic, because in most cases, this is exactly our actual task and ability. For example, we better leave the correction of operating system vulnerabilities to the experts in this field anyway. And perhaps you’ve already noticed that, regardless of the hyped technical aspects of serverless, this also makes it much easier for us to operate a real DevOps culture and agile software development.
However, it will also provide us with completely new prospects such as cost transparency at the transaction level, with which we can granularly protocol and aggregate the exact costs of individual user interactions. If we are the first product in our industry to deliver a pay on-demand cost model to our customers — instead of just a traditional monthly subscription with a fixed price per user like the competition — then this can be the key advantage over the competition. We can also measure the true cost of a particular feature for the first time and thus determine an accurate return on investment (ROI). We will hear more about this in the future under the keyword FinDev.
Serverless identity provider
Why don’t we use a self- and locally installable product, such as Identity Server [4] or Keycloak [5] as IDP? Well, following the “Serverless State of Mind” [6], we try, if possible, to use a managed serverless service also in the case of the IDP, in order to enjoy as many of the aforementioned advantages as possible, and above all else, to use a pay on-demand pricing model.
Why don’t we stay within the Microsoft Azure world and use Azure Active Directory (AAD) [7] or Azure Active Directory B2C (AAD B2C) [8] as IDP? We don’t want to commit ourselves too strongly to a certain IDP, but the described solution should be applicable for any IDP that supports OAuth 2. Auth0 represents one such exchangeable IDP that also offers a very simple and user-friendly management portal. The further advantages of Auth0 are the following, while AAD and AAD B2C, as well as other IDPs such as Okta, Ping Identity, One Login, or Centrify may only have these partially applicable:
- Free of charge with low usage (therefore, you can do the later examples yourself)
- Pay on demand for usage in excess of the free offer
- Integration of social IDPs such as Facebook, Twitter, Google, GitHub, etc.
- Integration of enterprise users who have their accounts stored in, for example, Active Directory (AD) or AAD
- Backend-to-backend communication via M2M clients
- Logical separation of APIs and applications that access APIs, such as Single Page Applications (SPA) or M2M clients
- Adaptability through rules, e.g. serverless code snippets, which are executed at special events
Which concrete managed serverless IDP you ultimately choose (or get selected) depends entirely on your individual needs and internal specifications. However, the following examples should also be transferable to other IDPs, albeit with more or less increased complexity regarding the respective configuration. Not only the IDP, but also the compute part can be implemented e.g. via AWS Lambda.
Creation of an Azure Function API
We are going to set the language setting of the Azure Portal, as well as the Auth0 Portal, to English, otherwise many terms just won’t make sense in Azure.
If you don’t have a Microsoft account, then you can create one on https://portal.azure.com by clicking on Create One! If logged into the Azure portal, search and select + Create a resource | Function App, then click start for free, and fill out the form. Unfortunately, you have to enter your credit card information here and log off and on again. Back at the creation of the Functions App (a grouping of several Azure Functions) we select the following settings:
- App name – for example, “e-m”
- OS: Windows
- Hosting Plan: Consumption Plan
- Location: West Europe (or customized to your location)
- Runtime Stack: Node.js
Then you have to activate Application Insights and set the location to West Europe. Leave the rest as it is and click on Create.
Once the function app has been created after a short time, we go to the resource, e.g. by clicking on Go to resource in the upper right corner under Notifications. Now we click left in the Functions menu on the + next to ▸ Functions and select In-portal (later you may want to switch to VS Code). Now click on Continue and select More templates… Then click on Finish and view templates and then on HTTP trigger. Enter “api-a” as name, leave the Authorization level at Function and click Create. We simplify the function a bit and copy the following code into it:
module.exports = async function (context, req) { context.log('Function api-a processed a request.'); context.res = { body: "Authentifizierung erfolgreich." }; };
This function is already secured with a Shared Secret by the previously selected Authorization Level Function. The shared secret concept is also known under names such as API Key, here it is called Function Key or Host Key.
Although we will immediately add a token validation, this protection must be maintained at all times. Otherwise, in the event of an attack, additional costs or a denial of service scenario could arise if you later use additional input bindings [9]. In contrast to Function Key Validation, the input bindings are executed before the actual code of our function, in which the token validation takes place.
Accessing the API
You are now using the HTTP client of your choice, e.g. the Postman [10] app or curl on the command line. The Visual Studio (Code) Extension REST Client [11] is used in the following examples to quickly and easily create and send HTTP requests. To access our newly created API, for example, you just have to type:
https://e-m.azurewebsites.net/api/api-a
However, this results in a 401 unauthorized response because we didn’t specify the Shared Secret, the Function Key. We can copy this in the sub-menu item Manage of the function api-a and insert it as HTTP header as follows:
https://e-m.azurewebsites.net/api/api-a x-functions-key: /7EivaQpDYRf9pdCS...
And voilà, now we get the response: “Authentication successful”.
Securing the API with Auth0
We need a representation of the API in Auth0 to secure the newly created Azure Function with Auth0. We can create a free Auth0 account at https://auth0.com under sign up. We choose an Auth0 tenant subdomain, e.g. “e-m”, receive as the Auth0 domain e-m.eu.auth0.com and end up in the dashboard of the management portal. In the menu on the left click on APIs, then on + Create API; for example select “API A” as name and “api://a” as identifier.
In our function, we have to specify values for the Auth0 and OAuth22 variables algorithms, domain, audience, and publicKey:
- algorithms: [“RS256”]
- domain: https://e-m.eu.auth0.com/ (the concluding / take not of)
- audience: api://a
- publicKey: can be found at the URL https://e-m.eu.auth0.com/pem
Back in the Azure Portal, we can change our Function api-a as described in Listing 1
Listing 1 const auth0ApiOptions = { algorithms: ["RS256"], domain: "https://e-m.eu.auth0.com/", audience: "api://a", publicKey: `-----BEGIN CERTIFICATE----- MIIDDTCCAfWgAwIBAgIJ.... ... <insert the complete public key here, incl. linebreak> ... -----END CERTIFICATE-----` }; const validateJwt = require("azure-functions-auth")(auth0ApiOptions); const main = async function (context, req) { context.log('Function api-a processed a request.'); if (req.user) { context.res = { status: 200, body: "Authentifizierung erfolgreich." }; } else { context.res = { status: 400, body: "Authentifizierung fehlgeschlagen." }; } context.done(); }; module.exports = validateJwt(main);
We use the library azure-functions-auth to validate an access token. We pack our main function into the Higher Order Function validateJwt(). To get successful access to our API, it is necessary to send a valid access token within the HTTP header.
Now that we’ve added a dependency, it needs to be installed first. In the Functions menu on the left we click on our Functions App, e.g. e-m to get to Overview, then on Platform features in the top right corner, and then on Console (CMD/PowerShell). There we enter the following command:
npm install azure-functions-auth
We now wait for the end of the installation process and ignore any typical npm warnings or error messages that may occur.
Accessing the Auth0-secured API
Now we will try to access the API without any changes.
https://e-m.azurewebsites.net/api/api-a x-functions-key: /7EivaQpDYRf9pdCS...
Once again, we get the response 401 “No authorization token was found”.
If we create a new API in Auth0, an M2M client is automatically created under Applications for API testing purposes. In our case the application API A (Test Application) exists now, and is already authorized to get access tokens for API A. In the Auth0 portal under APIs | API A | Test, an access token for this test application is helpfully generated and provided to us in the UI. A click on Copy Token copies it to the clipboard. Let’s use this token in the REST client,
https://e-m.azurewebsites.net/api/api-a x-functions-key: /7EivaQpDYRf9pdCS... Authorization: Bearer eyJ0eXAiOiJKV1QiL...
This time we get the response 200 “Authentication successful” – we have reached our goal.
The automatically created Auth0 test application is not intended for production environments, but only for test purposes. In the Auth0 portal, however, it is easy to create additional applications, such as Single Page Applications, Regular Web Applications (for frontends), or Machine to Machine (for backend-to-backend communication). No matter which type of application is to be accessed, our API is now optimally secured in any case.
Conclusion
You don’t have to shy away from serverless. Even if you have to rethink things here and there, it is worth investing in this architecture of the future. Although you no longer have to worry about operating system updates yourself, it remains necessary to have a basic awareness of security. With Azure Functions and Auth0, however, we have a simple and free way to try out important aspects of it.
Links & Literature
[1] OAuth 2: https://de.wikipedia.org/wiki/OAuth#OAuth_2.0_und_OpenID_Connect
[2] JsonWebToken: https://de.wikipedia.org/wiki/JSON_Web_Token
[3] Identity Provider: https://en.wikipedia.org/wiki/Identity_provider
[4] Identity Server: https://identityserver.io
[5] KeyCloak: https://www.keycloak.org
[6] Serverless State of Mind: https://www.youtube.com/watch?v=8Rzv68K8ZOY
[7] IDP Azure Active Directory: https://azure.microsoft.com/de-de/services/active-directory/
[8] Azure Active Directory B2C (AAD B2C): https://azure.microsoft.com/de-de/services/active-directory-b2c/
[11] https://marketplace.visualstudio.com/items?itemName=humao.rest-client